package by.brest.bspc.web.controllers;

import by.brest.bspc.domain.Student;
import by.brest.bspc.service.JasperReportsCreatorService;
import by.brest.bspc.service.StudentRepositoryService;
import by.brest.bspc.service.WorkLoadExtractorService;
import by.brest.bspc.web.TreeFileFilter;
import by.brest.bspc.web.TreeNode;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;



/**
 * Controller to processing the documentation page.
 *
 * @date: 9/14/13 3:16 PM
 * @author: frehty
 */
@Controller
public class DocumentsController {
    private static final Logger LOG = LoggerFactory.getLogger(DocumentsController.class);
    public static final String SUMMARY_REPORT_JASPER = "summary_report.jasper";
    public static final String DEGREE_OF_TRAINING_GROUPS_JASPER = "degree_of_training_groups.jasper";
    public static final String GROUP_NAME_KEY = "group_name";
    public static final String SEMESTER_KEY = "semester";
    public static final String YEARS_KEY = "years";
    public static final String FACULTY_NAME_KEY = "faculty_name";
    public static final String STUDY_YEAR_KEY = "study_year";
    public static final String BEGIN_DATE = "begin_date";
    public static final String END_DATE = "end_date";
    public static final String SEMESTER_NUMBER = "semester_number";
    public static final String SCHOLARSHIP_LIST_JASPER = "scholarship_list.jasper";
    public static final String STUDENT_NAME = "student_name";
    public static final String STUDY_CARD_JASPER = "study_card.jasper";
    public static final String CURRENT = "Current";
    public static final String ARCHIVE = "Archive";
    public static final String COLLEGE = "College";

    @Value("${documentation.directory}")
    private String documentationDirectory;

    @Value("${jasper.templates.directory}")
    private String jasperTemplateDirectory;

    @Value("${mongo.uri}")
    private String mongoUri;

    @Value("${groups.directory}")
    private String groupsDirectory;

    @Value("${workload.directory}")
    private String workloadDirectory;

    @Value("${degree.of.the.groups.training}")
    private String degreeOfTheGroupsTraining;

    @Value("${scholarships}")
    private String scholarships;

    @Value("${summary.sheet}")
    private String summarySheet;

    @Value("${training.cards}")
    private String trainingCards;

    @Resource
    private StudentRepositoryService studentRepositoryService;

    @Resource
    private JasperReportsCreatorService jasperReportsCreatorService;

    @Resource
    private WorkLoadExtractorService workloadExtractorService;

    private byte nesting = 0;

    /**
     * Prepares data for the main page.
     *
     * @param model model with the data for main page.
     * @return the name of the main page view.
     */
    @RequestMapping("/main")
    public String showDocumentsPage(Map<String, Object> model) {
        LOG.debug(">> Loading main page...");

        TreeFileFilter treeFileFilter = new TreeFileFilter(true);

        File currentDirectory = new File(documentationDirectory + File.separator + COLLEGE);

        LOG.debug(">> Current directory: " + currentDirectory.getAbsolutePath());

        model.put("tree", new TreeNode(COLLEGE, getTree(currentDirectory, (byte) 0, treeFileFilter)));

        LOG.debug(">> Total tree: " + model.get("tree"));

        return "documents";
    }

    /**
     * Prepares data for the frame with the list of the documents.
     *
     * @param path  parameters for generation path for searching.
     * @param model model with the data for the frame with the list of the documents.
     * @return the name of the view with the list of the documents.
     */
    @RequestMapping(value = "/documents", method = RequestMethod.GET)
    public String getDocuments(@RequestParam("path") String path, Map<String, Object> model) {
        getDocumentsFromDirectory(path, model);

        LOG.debug(">> Total documents map: " + model.get("documents"));

        return "documentsList";
    }

    /**
     * Removes documents.
     *
     * @param files files, that need to remove.
     * @param model model with the data for the frame with the list of the documents.
     * @return string to redirect on the view with the list of the documents.
     * @throws IOException
     */
    @RequestMapping(value = "/documents/remove", method = RequestMethod.POST)
    public String removeFiles(@RequestParam("files") String[] files, Map<String, Object> model) throws IOException {
        LOG.debug(">> Removing documents...");

        for (String file : files) {
            LOG.debug(">> Removing file: " + file);
            FileUtils.forceDelete(new File(file));
        }

        LOG.debug(">> All files have been removed.");

        // init parameter "path" to redirect page.
        File file = new File(files[0]);
        String path = new File(file.getParent()).getParent();
        path = path.substring(path.indexOf(COLLEGE), path.length());

        LOG.debug(">> Path to redirecting page: " + path);

        getDocumentsFromDirectory(path, model);

        LOG.debug(">> Total documents map: " + model.get("documents"));

        return "documentsList";
    }

    /**
     * Downloads document.
     *
     * @param path     path to file, that need to download.
     * @param response http response.
     * @param model    model with the data for the frame with the list of the documents.
     * @return string to redirect on the view with the list of the documents.
     * @throws IOException
     */
    @RequestMapping(value = "/documents/download", method = RequestMethod.GET)
    public String downloadDocument(@RequestParam("path") String path, HttpServletResponse response, Map<String, Object> model) throws IOException {
        LOG.debug(">> Downloading document...");
        LOG.debug(">> Path to document: " + path);

        File downloadableFile = new File(path);
        FileInputStream fis = new FileInputStream(downloadableFile);

        LOG.debug(">> Generating response...");

        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("UTF-8");
        response.setHeader("Content-Disposition", ((new StringBuilder("inline;filename="))
                .append(downloadableFile.getName())).toString());
        response.setContentLength((int) downloadableFile.length());

        LOG.debug(">> Response:");
        LOG.debug("  >> content type: " + response.getContentType());
        LOG.debug("  >> character encoding: " + response.getCharacterEncoding());

        IOUtils.copy(fis, response.getOutputStream());
        response.flushBuffer();

        getDocumentsFromDirectory(path.substring(path.indexOf(COLLEGE), path.indexOf(File.separator + "Documents_")).replace(File.separator, ","), model);

        LOG.debug(">> Total documents map: " + model.get("documents"));

        return "documentsList";
    }

    /**
     * Creates documents.
     *
     * @param documentPath path to a document in filesystem.
     * @param model        model with the data for the frame with the list of the documents.
     * @return string to redirect on the view with the list of the documents.
     */
    @RequestMapping(value = "/documents/create", method = RequestMethod.POST)
    public String createDocument(@RequestParam("document") String documentPath, Map<String, Object> model) {
        LOG.debug(">> Creating document...");
        LOG.debug(">> Path to document: " + documentPath);

        String returnedPath = documentPath.substring(0, documentPath.lastIndexOf(","));

        documentPath = documentPath.replace(",", File.separator);
        documentPath = documentationDirectory + File.separator + documentPath;

        LOG.debug(">> Total path to document: " + documentPath);

        Calendar calendar = Calendar.getInstance();
        int month = calendar.get(Calendar.MONTH);
        int year = calendar.get(Calendar.YEAR);

        if (documentPath.contains(summarySheet)) {
            String groupName = documentPath.substring(0, documentPath.indexOf(File.separator + summarySheet));
            groupName = groupName.substring(groupName.lastIndexOf(File.separator) + 1);

            LOG.debug(">> Group name: " + groupName);

            int currentClassNumber = studentRepositoryService.getCurrentClassByGroupName(groupName);

            LOG.debug(">> Current class number: " + currentClassNumber);

            int semester = (month == 0 || month >= 8) ? currentClassNumber * 2 - 1 : currentClassNumber * 2;

            LOG.debug(">> Current month: " + month + "; current semester: " + semester);

            String years = month >= 8 ? year + "-" + (year + 1) : (year - 1) + "-" + year;

            LOG.debug(">> Current month: " + month + "; current years: " + years);

            Map<String, Object> parametersMap = new HashMap<>();
            parametersMap.put(GROUP_NAME_KEY, groupName);
            parametersMap.put(SEMESTER_KEY, semester);
            parametersMap.put(YEARS_KEY, years);

            LOG.debug(">> Parameters map: " + parametersMap);

            jasperReportsCreatorService.createReport(jasperTemplateDirectory + File.separator + SUMMARY_REPORT_JASPER,
                    parametersMap,
                    documentPath + File.separator + "Summary_sheet_" + semester + "_semester_" + years + ".xlsx",
                    mongoUri);
        } else if (documentPath.contains(degreeOfTheGroupsTraining)) {
            String facultyName = documentPath.substring(0, documentPath.indexOf(File.separator
                    + degreeOfTheGroupsTraining));
            facultyName = facultyName.substring(facultyName.lastIndexOf(File.separator) + 1);

            LOG.debug(">> Faculty name: " + facultyName);

            int semester = (month == 0 || month >= 8) ? 1 : 2;

            LOG.debug(">> Current month: " + month + "; current semester: " + semester);

            String years = month >= 8 ? year + "-" + (year + 1) : (year - 1) + "-" + year;

            LOG.debug(">> Current month: " + month + "; current years: " + years);

            Map<String, Object> parametersMap = new HashMap<>();
            parametersMap.put(FACULTY_NAME_KEY, facultyName);
            parametersMap.put(SEMESTER_KEY, semester);
            parametersMap.put(STUDY_YEAR_KEY, years);

            LOG.debug(">> Parameters map: " + parametersMap);

            jasperReportsCreatorService.createReport(jasperTemplateDirectory + File.separator + DEGREE_OF_TRAINING_GROUPS_JASPER,
                    parametersMap,
                    documentPath + File.separator + "Degree_of_the_groups_training_" + semester + "_semester_" + years + ".xlsx",
                    mongoUri);
        } else if (documentPath.contains(scholarships)) {
            String facultyName = documentPath.substring(0, documentPath.indexOf(File.separator + scholarships));
            facultyName = facultyName.substring(facultyName.lastIndexOf(File.separator) + 1);

            LOG.debug(">> Faculty name: " + facultyName);

            int semester = (month == 0 || month >= 8) ? 1 : 2;

            LOG.debug(">> Current month: " + month + "; current semester: " + semester);

            String beginDate = calendar.get(Calendar.DAY_OF_MONTH) + "." + month + 1 + "." + year;
            String endDate = calendar.get(Calendar.DAY_OF_MONTH)
                    + "." + (month + 6 > 11 ? (month + 6) - 11 : month + 7)
                    + "." + (month + 6 > 11 ? (year + 1) : year);

            LOG.debug(">> Current month: " + month + "; begin date: " + beginDate + "; end date: " + endDate);

            Map<String, Object> parametersMap = new HashMap<>();
            parametersMap.put(BEGIN_DATE, beginDate);
            parametersMap.put(END_DATE, endDate);
            parametersMap.put(FACULTY_NAME_KEY, facultyName);
            parametersMap.put(SEMESTER_NUMBER, semester);

            LOG.debug(">> Parameters map: " + parametersMap);

            jasperReportsCreatorService.createReport(jasperTemplateDirectory + File.separator + SCHOLARSHIP_LIST_JASPER,
                    parametersMap,
                    documentPath + File.separator + "Scholarship_" + semester + "_semester_from_" + beginDate + "_to_" + endDate + ".xlsx",
                    mongoUri);
        } else if (documentPath.contains(trainingCards)) {
            StringBuilder path = new StringBuilder(documentPath.substring(0, documentPath.indexOf(File.separator + trainingCards)));
            String studentName = path.substring(path.lastIndexOf(File.separator) + 1);

            LOG.debug(">> Student name: " + studentName);

            path.delete(path.lastIndexOf(File.separator), path.length());
            String groupName = path.substring(path.lastIndexOf(File.separator) + 1);

            LOG.debug(">> Group name: " + groupName);

            path.delete(path.lastIndexOf(File.separator), path.length());
            String facultyName = path.substring(path.lastIndexOf(File.separator) + 1);

            LOG.debug(">> Faculty name: " + facultyName);

            Map<String, Object> parametersMap = new HashMap<>();
            parametersMap.put(STUDENT_NAME, studentName);
            parametersMap.put(GROUP_NAME_KEY, groupName);

            LOG.debug(">> Parameters map: " + parametersMap);

            jasperReportsCreatorService.createReport(jasperTemplateDirectory + File.separator + STUDY_CARD_JASPER,
                    parametersMap,
                    documentPath + File.separator + "Training_card_" + studentName + "_" + groupName + "_" + (month >= 8 ? year + "-" + (year + 1) : (year - 1) + "-" + year) + ".xlsx",
                    mongoUri);
        }
        getDocumentsFromDirectory(returnedPath, new HashMap<String, Object>());

        LOG.debug(">> Total documents map: " + model.get("documents"));

        return "documentsList";
    }

    /**
     * Uploads the students workload.
     * Includes three steps:
     * 1. Update graduated students in the database (change status).
     * 2. Move graduated groups in archive.
     * 3. Upload workload in the database.
     *
     * @return string to redirect on the view with the list of the documents.
     */
    @RequestMapping(value = "/documents/uploadworkload")
    public String uploadWorkload() {
        // step 1: update graduated students.
        List<String> graduatedGrops = studentRepositoryService.updateGraduatedStudents();

        // step 2: move graduated groups in archive.
        File constructionFaculty = null;
        File machineFaculty = null;
        File lawFaculty = null;
        File radioFaculty = null;
        File constructionFacultyArchive = null;
        File machineFacultyArchive = null;
        File lawFacultyArchive = null;
        File radioFacultyArchive = null;
        if (!graduatedGrops.isEmpty()) {
            constructionFaculty = new File(documentationDirectory, COLLEGE + File.separator + CURRENT + File.separator + "Строительное отделение");
            machineFaculty = new File(documentationDirectory, COLLEGE + File.separator + CURRENT + File.separator + "Машиностроительное отделение");
            lawFaculty = new File(documentationDirectory, COLLEGE + File.separator + CURRENT + File.separator + "Юридическое отделение");
            radioFaculty = new File(documentationDirectory, COLLEGE + File.separator + CURRENT + File.separator + "Радиотехническое отделение");
            constructionFacultyArchive = getFacultyArchiveDirectory("Строительное отделение");
            machineFacultyArchive = getFacultyArchiveDirectory("Машиностроительное отделение");
            lawFacultyArchive = getFacultyArchiveDirectory("Юридическое отделение");
            radioFacultyArchive = getFacultyArchiveDirectory("Радиотехническое отделение");
        }

        for (String graduatedGroup : graduatedGrops) {
            try {
                if (Arrays.asList(constructionFaculty.listFiles()).contains(graduatedGroup)) {
                    FileUtils.moveDirectory(new File(constructionFaculty, graduatedGroup), constructionFacultyArchive);
                } else if (Arrays.asList(machineFaculty.listFiles()).contains(graduatedGroup)) {
                    FileUtils.moveDirectory(new File(machineFaculty, graduatedGroup), machineFacultyArchive);
                } else if (Arrays.asList(lawFaculty.listFiles()).contains(graduatedGroup)) {
                    FileUtils.moveDirectory(new File(lawFaculty, graduatedGroup), lawFacultyArchive);
                } else {
                    FileUtils.moveDirectory(new File(radioFaculty, graduatedGroup), radioFacultyArchive);
                }
            } catch (Exception e) {
                LOG.debug(">> Error moving graduated groups.", e);
            }
        }

        // step 3: upload workload.
        File groups = new File(groupsDirectory);
        File workload = new File(workloadDirectory);
        List<File> groupFiles = Arrays.asList(groups.listFiles());
        for (File groupFile : groupFiles) {
            List<Student> students = workloadExtractorService.extractStudentsWorkload(groups, workload,
                    groupFile.getName().substring(0, groupFile.getName().indexOf(".doc")));
            //  Create directories
            String faculty = students.get(0).getFaculty();
            String group = students.get(0).getGroupName();

            createDirectory(faculty + File.separator + group + File.separator + summarySheet);
            createDirectory(faculty + File.separator + degreeOfTheGroupsTraining);
            createDirectory(faculty + File.separator + scholarships);
            //  Save students and create directories
            for (Student student : students) {
                studentRepositoryService.saveStudent(student);
                createDirectory(faculty + File.separator + group
                        + File.separator + student.getName() + File.separator + trainingCards);
            }
        }

        return "redirect:/main";
    }

    /**
     * Obtains faculty directory in the archive.
     * If this directory doesn't exists, creates it.
     *
     * @param faculty faculty name.
     * @return faculty directory.
     */
    private File getFacultyArchiveDirectory(String faculty) {
        File facultyArchive = new File(documentationDirectory, COLLEGE + File.separator + ARCHIVE + File.separator + faculty);
        if (!facultyArchive.exists()) {
            facultyArchive.mkdirs();
        }
        return facultyArchive;
    }

    /**
     * Creates directory in the current section.
     *
     * @param directory directory, that should be created.
     */
    private void createDirectory(String directory) {
        File facultyDirectory = new File(documentationDirectory, COLLEGE + File.separator
                + CURRENT + File.separator + directory);
        if (!facultyDirectory.exists()) {
            facultyDirectory.mkdirs();
        }
    }

    /**
     * Gets all documents from specified directory.
     *
     * @param path  path to directory with documents.
     * @param model model with the data for the frame with the list of the documents.
     */
    private void getDocumentsFromDirectory(String path, Map<String, Object> model) {
        LOG.debug(">> Obtaining documents...");
        LOG.debug(">> Parameter path: " + path);

        TreeFileFilter treeFileFilter = new TreeFileFilter(false);
        StringBuilder totalPath = new StringBuilder(documentationDirectory);

        String[] pathElements = path.split(",");
        for (String pathElement : pathElements) {
            totalPath.append(File.separator).append(pathElement);
        }

        LOG.debug(">> Looking for documents in directory: " + totalPath.toString());

        File currentDirectory = new File(totalPath.toString());
        List<File> documentsDirectoryList = Arrays.asList(currentDirectory.listFiles(treeFileFilter));

        LOG.debug(">> Documents in this directory: " + documentsDirectoryList);

        Map<String, List<File>> documentsMap = new HashMap<>();
        for (File documentsDirectory : documentsDirectoryList) {
            documentsMap.put(documentsDirectory.getName().substring("Documents_".length(), documentsDirectory.getName().length()), Arrays.asList(documentsDirectory.listFiles()));
        }

        model.put("documents", documentsMap);
        model.put("directory", totalPath.substring(documentationDirectory.length() + 1).replace(File.separator, ","));
    }
    /**
     * Gets hierarchy of directories.
     *
     * @param currentDirectory the main node.
     * @param nesting          current nesting of directories.
     * @param treeFileFilter   filter to selection directories.
     * @return total tree of directories.
     */
    private List<TreeNode> getTree(File currentDirectory, byte nesting, TreeFileFilter treeFileFilter) {
        LOG.debug(">> Getting files from directory " + currentDirectory);
        LOG.debug(">> Nesting: " + nesting);
        LOG.debug(">> Tree file filter: " + treeFileFilter);

        List<TreeNode> tree = new ArrayList<>();

        List<File> subDirectories = Arrays.asList(currentDirectory.listFiles(treeFileFilter));
        for (File currentSubDirectory : subDirectories) {
            LOG.debug(">> Current sub directory: " + currentSubDirectory);

            List<TreeNode> treeNodes = null;
            if (nesting < 4) {
                treeNodes = getTree(currentSubDirectory, ++nesting, treeFileFilter);
                --nesting;
                LOG.debug(">> Obtained list of the tree nodes: " + treeNodes);
            }

            tree.add(new TreeNode(currentSubDirectory.getName(),
                    (nesting < 4) ? treeNodes : new ArrayList<TreeNode>()));
        }

        LOG.debug(">> Tree for directory " + currentDirectory + ": " + tree);

        return tree;
    }

}